﻿using SharpDepend.Storage;
using SoundEngine3.DspPlugins.Meta;
using System;
using System.Collections.Generic;
using System.Text;
using ZquenceStudio3.Core.Components.Calculator;

namespace ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Modules.HighLevel
{
    [ModuleDisplay("Algorithm")]
    public class CustomAlgorithmModule : BaseModule
    {
        public readonly ModuleInput A;
        public readonly ModuleInput B;
        public readonly ModuleInput C;
        public readonly ModuleInput X;
        public readonly ModuleInput Y;
        public readonly ModuleInput Z;
        public readonly ModuleOutput Out;

        StringBuilder mSB = new StringBuilder();

        public string Algorithm
        {
            get
            {
                return mExpression;
            }
            set
            {
                for (int i = 0; i < value.Length; i++)
                {
                    if (!char.IsWhiteSpace(value[i]))
                    {
                        mSB.Append(value[i]);
                    }
                }

                mExpression = value;
                mCalculator.Expression = mExpressionOptimized = mSB.ToString();
                mSB.Clear();
            }
        }

        private CalculatorEngine mCalculator;
        private ModuleParameter algorithmStringParameter;
        private double[] mTime;
        private int mNextSample;
        private string mExpression;
        private string mExpressionOptimized;

        public CustomAlgorithmModule(ModuleManager manager)
            : base(manager)
        {
            // Add input and outputs to module.
            AddInput(A = new ModuleInput("A", IOType.Audio, this));
            AddInput(B = new ModuleInput("B", IOType.Audio, this));
            AddInput(C = new ModuleInput("C", IOType.Audio, this));
            AddInput(X = new ModuleInput("X", IOType.Audio, this));
            AddInput(Y = new ModuleInput("Y", IOType.Audio, this));
            AddInput(Z = new ModuleInput("Z", IOType.Audio, this));
            AddOutput(Out = new ModuleOutput("Out", IOType.Audio, this));

            A.OnConnected += OnConnected;
            A.OnDisconnected += OnDisconnected;
            B.OnConnected += OnConnected;
            B.OnDisconnected += OnDisconnected;
            C.OnConnected += OnConnected;
            C.OnDisconnected += OnDisconnected;
            X.OnConnected += OnConnected;
            X.OnDisconnected += OnDisconnected;
            Y.OnConnected += OnConnected;
            Y.OnDisconnected += OnDisconnected;
            Z.OnConnected += OnConnected;
            Z.OnDisconnected += OnDisconnected;
            
            algorithmStringParameter = new ModuleParameter(this);
            algorithmStringParameter.Label = "Expression";
            algorithmStringParameter.Flags = ParameterPropertiesFlags.ParameterString;
            algorithmStringParameter.DirectGetObject += AlgorithmStringParameter_DirectGetObject;
            algorithmStringParameter.DirectSetObject += AlgorithmStringParameter_DirectSetObject;
            AddParameter(algorithmStringParameter);

            ResetTimeArray(SoundEngineContext.BufferSize);

            // Defalt algorithm.
            Algorithm = "sin(T*0.1)+A";
        }

        Dictionary<char, WaveFormBuffer> mAttachedParameters = new Dictionary<char, WaveFormBuffer>();

        public override void SetData(CustomNode data)
        {
            data.AddProperty("algo", Algorithm);
        }

        public override void GetData(CustomNode data)
        {
            Algorithm = data.GetPropertyValue("algo");
        }

        void ResetTimeArray(int bufferSize)
        {
            mCalculator = new CalculatorEngine(bufferSize);
            mCalculator.Expression = mExpressionOptimized;

            mTime = new double[SoundEngineContext.BufferSize];
            mCalculator['T'] = mTime;

            // Re-attach parameters.
            foreach (var attachedParameter in mAttachedParameters)
            {
                if (bufferSize != attachedParameter.Value.Buffer.Length)
                {
                    throw new Exception("New buffer size do not match attached parameters. Can not re attach parameter for module.");
                }

                mCalculator[attachedParameter.Key] = attachedParameter.Value.Buffer;
            }
        }
        
        private void OnConnected(ModuleOutput output, ModuleInput input)
        {
            char key = input.Name[0];
            mAttachedParameters.Add(key, output.AudioBuffer);
            mCalculator[key] = output.AudioBuffer.Buffer;
        }

        private void OnDisconnected(ModuleOutput output, ModuleInput input)
        {
            char key = input.Name[0];
            mAttachedParameters.Remove(key);
            mCalculator[key] = null;
        }

        private object AlgorithmStringParameter_DirectGetObject()
        {
            return Algorithm;
        }

        private void AlgorithmStringParameter_DirectSetObject(object value)
        {
            string expression = (string)value;

            Algorithm = expression;
        }

        public override void BufferSizeChanged(int bufferSize)
        {
            ResetTimeArray(bufferSize);
        }

        public override void Process()
        {
            try
            {
                int s = mNextSample;
                for (int i = 0; i < mTime.Length; i++)
                {
                    mTime[i] = s++;
                }
                mNextSample = s;

                mCalculator.GetResults(Out.AudioBuffer.Buffer);
            }
            catch (Exception error)
            {
                Out.AudioBuffer.Clear();
            }
        }
    }
}
